var gamejs = require("gamejs");
var mapModule = require("js/map");
var utils = require("js/utils");
var Radar = require("js/radar").Radar;
var Turret = require("js/turret").Turret;
var Obstacle = require("js/obstacle").Obstacle;
var missions = require("js/missions");

/**
 * Match : an object that stores all the match info: credits, health points,
 * score and the map.
 */

/**
 * Creates a match.
 *@param {SoundManager} The soundManager for the match.
 * @constructor
 */
var Match = exports.Match = function(soundManager) {
	this.credit = 0;
	this.mission = 0;
	this.level = 0;
	this.fullHealth = INITIAL_HEALTH;
	this.health = INITIAL_HEALTH;
	this.inGame = false;
	this.score = 0;
	this.soundManager = soundManager;
	this.mapSurface = new gamejs.Surface(MAP_WIDTH, MAP_HEIGHT);
	this.font = new gamejs.font.Font("20px Verdana");
	this.levelStore = null;
	this.missionStore = null;
};

/**
 * Starts the match.
 *
 * @throws {ReferenceError} If the map isn't assigned.
 */
Match.prototype.start = function() {
	this.nextMission();
	this.inGame = true;
};

/**
 * Returns the level the player is playing.
 *
 * @return {number} The level that the player is playing, at first level
 * it returns 1.
 */
Match.prototype.getLevel = function() {
	return this.level;
};

/**
 * Returns the mission the player is playing.
 *
 * @return {number} The mission that the player is playing, at first mission
 * it returns 1.
 */
Match.prototype.getMission = function() {
	return this.mission;
};

/**
 * Returns true if the player is playing the last level of the mission.
 *
 * @return {boolean} True if is the last level of the mission, false otherwise.
 */
Match.prototype.isLastLevel = function() {
	return (this.level == missions.MISSIONS[this.getMission() - 1].levels.length);
};

/**
 * Returns true if the player is playing the last mission.
 *
 * @return {boolean} True if is the last mission, false otherwise.
 */
Match.prototype.isLastMission = function() {
	return (this.mission == missions.MISSIONS.length);
};

/**
 * Increment the level if is not the last level of the mission.
 */
Match.prototype.nextLevel = function() {
	if( ! this.isLastLevel()) {
		this.level += 1;
		// we hold the map but have to remove explosions and projectiles
		this.map.getExplosions().empty();
		this.map.getProjectiles().empty();
		this.storeBeforeLevel();
	}
};

/**
 * Restarts the current level.
 * Using this you delete the game progress of the level.
 */
Match.prototype.restartLevel = function() {
	this.restoreLevel();
};

/**
 * Increment the mission if is not the last and set the current level
 * to 1.
 */
Match.prototype.nextMission = function() {
	if( ! this.isLastMission()) {
		this.mission += 1;
		this.level = 0;
		this.credit = missions.MISSIONS[this.mission - 1].startingCredits;
		this.storeBeforeMission();
		this.setMap(mapModule.createMissionMap(this));
		this.nextLevel();
	}
};

/**
 * Restarts the current mission from the first level.
 * Using this you delete the game progress of the mission.
 */
Match.prototype.restartMission = function() {
	this.level = 0;
	this.restoreMission();
	this.setMap(mapModule.createMissionMap(this));
	this.nextLevel();
};

/**
 * Stores the match info so you can restore them when restarting a level.
 *
 * This makes a full and deep copy of this Match object cause we need to store
 * also the Map object and all its content.
 *
 * NOTE : this method MUST NOT be used out of the Match object, don't worry
 * of storing or restoring, these actions are automatically done when
 * using the levels management methods.
 */
Match.prototype.storeBeforeLevel = function() {
	var obs = this.map.getObstacles().sprites();
	var rad = this.map.getRadars().sprites();
	var tur = this.map.getTurrets().sprites();
	
	var geList = obs.concat(rad, tur);
	
	this.levelStore = {
		health : this.health,
		credit : this.credit,
		score : this.score,
		gameEntity : geList
	};
};

/**
 * Restores the match info stored with the storeBeforeLevel() method.
 *
 * NOTE : this method MUST NOT be used out of the Match object, don't worry
 * of storing or restoring, these actions are automatically done when
 * using the levels management methods.
 */
Match.prototype.restoreLevel = function() {
	// restore the map and its content
	this.map = new mapModule.Map(this, MAP_WIDTH, MAP_HEIGHT);
	
	// add credit so we can add all what we want
	this.credit = Number.POSITIVE_INFINITY;
	this.levelStore.gameEntity.forEach(function (ge) {
		var newGe;
		var center = ge.getCenter();
		if(ge instanceof Obstacle) {
			newGe = new Obstacle(this, center, ge.getCreationLevel());
		}
		else if(ge instanceof Radar) {
			newGe = new Radar(this, center, ge.getStartAngle(), ge.getCentralAngle());
		}
		else if(ge instanceof Turret) {
			newGe = new ge.constructor(this, center, ge.getTargettingFunction());
		}
		else {
			throw new Error("Can't restore this type to the map");
		}
		// to the right level
		for(i = 0; i < ge.getLevel(); i++) {
			newGe.upgrade();
		}
		// add to the map
		this.map.addGameEntity(newGe);
	}, this);
	
	this.health = this.levelStore.health;
	this.credit = this.levelStore.credit;
	this.score = this.levelStore.score;
	
	this.inGame = true;
};

/**
 * Stores the match info so you can restore them when restarting a mission.
 *
 * This stores only the match info (credit, health, score) because these
 * are the only things that remain through different missions.
 *
 * NOTE : this method MUST NOT be used out of the Match object, don't worry
 * of storing or restoring, these actions are automatically done when
 * using the missions management methods.
 */
Match.prototype.storeBeforeMission = function() {
	this.missionStore = {
		health : this.health,
		score : this.score
	};
};

/**
 * Restores the match info stored with the storeBeforeMission() method.
 *
 * NOTE : this method MUST NOT be used out of the Match object, don't worry
 * of storing or restoring, these actions are automatically done when
 * using the missions management methods.
 */
Match.prototype.restoreMission = function() {
	this.health = this.missionStore.health;
	this.score = this.missionStore.score;
	this.credit = missions.MISSIONS[this.mission - 1].startingCredits;
	this.inGame = true;
};

/**
 * Returns the map used by this match.
 *
 * @return {Map} The Map object used by this match.
 * @throws {ReferenceError} If the map isn't assigned yet.
 */
Match.prototype.getMap = function() {
	if(this.map) {
		return this.map;
	}
	else {
		throw new ReferenceError("Map not assigned.");
	}
};

/**
 * Returns the SoundManager object used by this match.
 *
 * @return {SoundManager} The SoundManager object used by this match.
 */
Match.prototype.getSoundManager = function() {
	return this.soundManager;
};

/**
 * Sets the map for this match.
 *
 * @param {Map} map The Map object to use in this match.
 * @throws {TypeError} If map isn't a Map object.
 */
Match.prototype.setMap = function(map) {
	if(! (map instanceof mapModule.Map)) {
		throw new TypeError("map isn't a Map object");
	}
	this.map = map;
};

/**
 * Returns the player's credit.
 *
 * @return {number} The amount of the player's credit.
 */
Match.prototype.getCredit = function() {
	return this.credit;
};

/**
 * Returns the score reached by the player in this match.
 *
 * @return {number} The score.
 */
Match.prototype.getScore = function() {
	return this.score;
};

/**
 * Returns the planet health.
 *
 * @return {number} The number of health points remaining to the planet.
 */
Match.prototype.getHealth = function() {
	return this.health;
};

/**
 * Checks if the player is in game.
 *
 * @returns {boolean} False if the player has lost, true otherwise.
 */
Match.prototype.isInGame = function() {
	return this.inGame;
};

/**
 * Decreases health of one or of the given amount, it also returns
 * the remaining health.
 *
 * @param {undefined|number} hitpoint Removes one health point if undefined, the given amount otherwise.
 * @return {number} The remaining health points.
 * @throws {TypeError} If hitpoint is defined but isn't a number.
 * @throws {RangeError} If hitpoint is lesser than zero.
 */
Match.prototype.decreaseHealth = function(hitpoint) {
	if(typeof hitpoint != "number" || isNaN(hitpoint)) {
		if(typeof hitpoint == "undefined") {
			this.health--;
		}
		else {
			throw new TypeError("hitpoint isn't a number or is NaN");
		}
	}
	else {
		if(hitpoint >= 0) {
			this.health -= hitpoint;
		}
		else {
			throw new RangeError("hitpoint is lesser than zero");
		}
	}
	
	if(this.health <= 0) {
		//there aren't more health points, player lost
		this.health = 0;
		this.inGame = false;
	}
	
	return this.health;
};

/**
 * Changes the credit by the given amount, it also returns the new credit.
 *
 * @param {number} amount The amount of the credit change.
 * @return {number} The new credit points.
 * @throws {TypeError} If amount isn't a number or is NaN.
 * @throws {RangeError} If there aren't enough credit.
 */
Match.prototype.changeCredit = function(amount) {
	if(typeof amount != "number" || isNaN(amount)) {
		throw new TypeError("amount isn't a number or is NaN");
	}
	
	if((amount < 0) && ( ! this.hasCredit(-amount))) {
		throw new RangeError("Not enough credit");
	}
		
	this.credit += amount;
	
	return this.credit;
};

/**
 * Checks if player has enough credits.
 *
 * @param {number} amount The amount of the credits to check.
 * @return {boolean} True if the player has at least the given
 * amount of credits, false otherwise.
 * @throws {TypeError} If amount isn't a number or is NaN.
 * @throws {RangeError} If amount is lesser than zero.
 */
Match.prototype.hasCredit = function(amount) {
	if(typeof amount != "number" || isNaN(amount)) {
		throw new TypeError("amount isn't a number or is NaN");
	}
	
	if(amount < 0) {
		throw new RangeError("amount is lesser than zero");
	}
	
	return this.credit >= amount;
};

/**
 * Changes the score by the given amount, it also returns the new score.
 *
 * @param {number} amount The amount of the score change.
 * @return {number} The new score.
 * @throws {TypeError} If amount isn't a number or is NaN.
 * @throws {RangeError} If with this amount the score become negative.
 */
Match.prototype.changeScore = function(amount) {
	if(typeof amount != "number" || isNaN(amount)) {
		throw new TypeError("amount isn't a number or is NaN");
	}
	
	if((this.score + amount) < 0) {
		throw new RangeError("amount makes score lesser than zero");
	}
		
	this.score += amount;
	
	return this.score;
};

/**
 * Updates the data in the game.
 *
 * @param {number} msDuration The time past from the last call, in ms.
 */
Match.prototype.update = function(msDuration) {
	if(this.map) {
		this.map.update(msDuration);
	}
};

/**
 * Draws the map and the overlay information on the given surface.
 *
 * @param {gamejs.Surface} The Surface object where to draw.
 */
Match.prototype.draw = function(surface) {
	if(this.map) {
		this.map.draw(this.mapSurface);
	}
	else {
		this.mapSurface.fill("#000000");
	}
	//draws the map in the correct position
	surface.blit(this.mapSurface, [WIDTH_OFFSET, HEIGHT_OFFSET]);
	
	//draws/writes the overlay information
	var src = this.font.render("Health : " + this.health, "#FFFFFF");
	gamejs.draw.line (
			surface, 
			"rgb("+Math.round(255 * ( this.fullHealth - this.health) / this.fullHealth)+", "+Math.round(255 * this.health / this.fullHealth)+", 0)", 
			[100, 25], 
			[100+ (this.health/ this.fullHealth)*200,25], 
			5
			);
	//utils.drawAtMiddle(src, surface, [190, 25]);
	src = this.font.render("Score : " + this.score, "#FFFFFF");
	utils.drawAtMiddle(src, surface, [480, 25]);
	src = this.font.render("Credit : " + this.credit, "#FFFFFF");
	utils.drawAtMiddle(src, surface, [680, 25]);
};